package core

type Lexer struct {
	input        string
	position     int  // current position in input (points to current char)
	readPosition int  // current reading position in input (after current char)
	ch           byte // current char under examination
	line         int  // current line number
	column       int
}

func NewLexer(input string) *Lexer {
	lx := &Lexer{input: input, readPosition: 0, line: 1, column: 0}
	lx.readChar()
	return lx
}

func (l *Lexer) readChar() {
	if l.readPosition >= len(l.input) {
		l.ch = 0 // EOF
		return
	}
	l.ch = l.input[l.readPosition]
	l.position = l.readPosition
	l.readPosition++
	l.column++

	// Update line and column for newlines
	if l.ch == '\n' {
		l.line++
		l.column = 0
	}
}

func (l *Lexer) ReadTokens() []Token {
	var tokens []Token
	for {
		token := l.NextToken()
		tokens = append(tokens, token)
		if token.Type == EOF {
			break
		}
	}
	return tokens
}
func (l *Lexer) NextToken() Token {
	var token Token

	l.skipSpace()

	// Check for special characters and delimiters
	switch l.ch {
	case '(':
		token = Token{LPAREN, "(", l.line, l.column}
		l.readChar()
	case ')':
		token = Token{RPAREN, ")", l.line, l.column}
		l.readChar()
	case '#':
		var lexeme string
		if l.peekChar() == 't' {
			lexeme = "#t"
		} else if l.peekChar() == 'f' {
			lexeme = "#f"
		} else {
			token = Token{ILLEGAL, string(l.ch), l.line, l.column}
			l.readChar()
			return token
		}
		token = Token{BOOLEAN, lexeme, l.line, l.column + 1}
		l.readChar()
		l.readChar()
	case 0:
		token = Token{Type: EOF}
	default:
		position := l.position
		for !isDelimiter(l.peekChar()) && !l.isEOF() {
			l.readChar()
		}
		text := l.input[position:l.readPosition]

		token = l.readSymbol(text)

		l.readChar()
	}

	return token
}

func (l *Lexer) skipSpace() {
	for isSpace(l.ch) {
		l.readChar()
	}
}

func isDelimiter(ch byte) bool {
	return isSpace(ch) || ch == '(' || ch == ')'
}

func isSpace(ch byte) bool {
	return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}

func (l *Lexer) readSymbol(text string) Token {
	t := SYMBOL
	for _, ch := range []byte(text) {
		if !isLetter(ch) && !isDigit(ch) && !isSymbolChar(ch) {
			t = ILLEGAL
		}
	}
	token := Token{t, text, l.line, l.column}

	return token
}

func (l *Lexer) peekChar() byte {
	if l.readPosition >= len(l.input) {
		return 0
	}

	return l.input[l.readPosition]
}

func isLetter(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}

func isDigit(ch byte) bool {
	return '0' <= ch && ch <= '9'
}

func isSymbolChar(ch byte) bool {
	switch ch {
	case '+', '-', '*', '/', '=', '<', '>', '!', '&', '|', ';', ',':
		return true
	default:
		return false
	}
}

func (l *Lexer) isEOF() bool {
	return l.readPosition >= len(l.input)
}
